import _thread
import ctypes
import pathlib
import sys
import threading
import time

import cv2
import pyautogui as pyautogui
import pynput as pynput
import pyscreeze
import requests as requests
import win32api
import win32con
import win32gui
from loguru import logger
from multiprocessing import Process
from pymouse import *

import entity.Project
import entity.Image
from entity.Operation import *


class Log:
    def __init__(self, project_path, det):
        self.root = det
        logger.add(project_path + "/det.log", rotation="1 week")

    @staticmethod
    def info_log(message):
        logger.info(message)

    @staticmethod
    def warn_log(message):
        logger.warning(message)

    @staticmethod
    def err_log(message):
        logger.error(message)

    def end(self):
        """正常退出"""
        self.show()
        self.info_log(f"正常退出，项目耗时："
                      f"{(time.time() - self.root.time)}秒")
        sys.exit(1)

    def err(self):
        """异常退出"""
        self.info_log(f"异常退出，项目耗时："
                      f"{(time.time() - self.root.time)}秒")
        sys.exit(1)

    @staticmethod
    def hide():
        """隐藏窗口"""
        pass

    def show(self):
        """展示窗口"""
        self.root.project.config.move_app()
        ctypes.windll.user32.ShowWindow(ctypes.windll.kernel32.GetConsoleWindow(), 5)


class Det:
    def __init__(self, ddt=None, project=None):
        self.root = None
        self.project = None
        self.process = None
        self.is_ddt = False
        self.id = 0
        self.time = None
        self.thread = None
        self.end = False
        # 场景列表
        self.scene = []
        if project is not None:
            """不使用ddt工具运行det"""
            self.root = Log(project, self)
            self.project = project

            self.project_path = pathlib.Path(self.project)
            self.config_path = pathlib.Path(self.project + "/app.dsft.ini")
            # 载入配置文件
            if not self.load_conf():
                self.root.err_log("载入配置文件检查失败")
                sys.exit(1)
        if ddt is not None:
            """通过ddt运行det"""
            self.is_ddt = True
            self.root = ddt
            self.project = ddt.project
            self.project_path = pathlib.Path(str(self.project.project))
            self.config_path = pathlib.Path(str(self.project.config.config_path))

    def check(self):
        """检查运行配置"""
        self.root.info_log("项目文件检查")
        if not self.project_path.is_dir():
            self.root.warn_log("没有找到项目路径")
            return False
        self.root.info_log("项目路径检查通过")
        if not self.config_path.is_file():
            self.root.warn_log("没有找到项目配置文件路径")
            return False
        self.root.info_log("项目配置文件路径检查通过")
        return True

    def load_conf(self):
        """把配置文件载入内存"""
        self.root.info_log("载入配置文件检查")
        try:
            self.project = entity.Project.Project(self.project_path, self.config_path)
            self.project.config.read_config()
        except Exception as e:
            self.root.warn_log(f"捕获到异常 {e}")
            return False
        self.root.info_log("载入配置文件检查通过")
        return True

    def thread_app(self):
        """监视应用程序是否存在的线程"""
        _thread.start_new_thread(self.inspect_app, ())

    def inspect_app(self):
        """监视应用程序是否存在的线程"""
        while 1:
            if self.end:
                return "end"
            time.sleep(0.2)
            if len(self.project.config.message_set) > 0:
                self.root.info_log(self.project.config.message_set.pop())
            if self.project.config.app is False:
                self.root.err_log(f"没有检查到‘{self.project.config.app_name}’，进程异常终止")
                self.root.err()

    def set_process(self):
        """创建本地进程"""
        self.process = Process(target=self.local_process, args=())
        self.process.start()

    def set_thread(self):
        """创建本地线程"""
        self.thread = threading.Thread(target=self.local_process, args=())
        self.thread.setDaemon(True)
        self.thread.start()

    def local_process(self):
        """本地进程"""
        self.time = time.time()
        for i in self.project.config.run_order:
            if self.end:
                return "end"
            scene_dict = self.a_scene_jpgs_to_list()
            self.root.info_log(f"进入场景{scene_dict['name']}")
            self.while_scene(scene_dict)

    def while_scene(self, scene_dict):
        """循环找图，直到超时或者找到特征跳转"""
        start_time = time.time()
        while 1:
            if self.end:
                return "end"
            if time.time() - start_time >= float(scene_dict["delay_time"]) * 60:
                self.root.info_log(f"超时({scene_dict['delay_time']}分)退出场景{scene_dict['name']}")
                return True
            pyscreeze.screenshot("screen.jpg")
            screen = gray(cv2.imread('screen.jpg'))
            for i in self.scene:
                _scene = scene_dict['name']
                try:
                    result = find_position(screen, i.img_cv, float(i.threshold))
                except TypeError:
                    self.root.err_log(f"捕获到有损坏的图片，位置在第{self.scene.index(i)+1}个，"
                                      f"请用ddt工具查看")
                    self.root.err()
                if result:
                    self.root.info_log(f"{i.scene}/{i.desc}已识别")
                    self.execute(i.operation, result)
                    _next = self.jump_scene(i.desc, scene_dict)
                    if _next:
                        self.root.info_log(f"准备跳转场景{i.desc}->{_next}")
                        scene_dict = self.next(_next)
                    if not scene_dict:
                        self.root.err_log(f"找不到跳转场景{i.desc}->???")
                        self.root.err()
                    if scene_dict == "end":
                        return "end"
                    if scene_dict['name'] == _scene:
                        continue
                    self.root.info_log(f"进入场景{scene_dict['name']}")
                    scene_dict = self.a_scene_jpgs_to_list(scene_dict)
                    self.while_scene(scene_dict)

    def next(self, _next):
        """加载下一个跳转"""
        for scene_dict in self.project.config.run_order:
            if scene_dict["name"] == _next:
                return scene_dict
            if _next == "正常终止":
                self.root.end()
            if _next == "异常终止":
                self.root.err()
        if self.end:
            return "end"
        return False

    @staticmethod
    def jump_scene(name, scene_dict):
        """查询跳转"""
        if name in scene_dict["jump"]:
            _next = scene_dict["jump"][name]
            return _next
        return False

    def a_scene_jpgs_to_list(self, _dict=False):
        """把一个场景中的特征载入场景列表"""
        if _dict:
            scene_dict = _dict
        else:
            scene_dict = self.get_next_scene()
        scene_path = self.project.project / scene_dict["name"]
        self.scene = []
        for i in scene_path.rglob("*.jpg"):
            if i.is_file():
                _image = entity.Image.Image()
                _image.set(i.absolute())
                self.scene.append(_image)
        return scene_dict

    def run(self):
        """运行"""
        # 检查运行配置
        if not self.check():
            self.root.err_log("项目文件检查失败")
            return False
        self.root.info_log("检查运行配置通过")
        # 阻塞1秒，使得线程查询应用是否打开
        time.sleep(1)
        # 应用程序是否打开，如果没有等待...
        if self.project.config.app is False:
            self.root.warn_log("没有找到应用程序，请手动打开，等待中...3分钟后自动退出程序")
        start_time = time.time()
        while 1:
            if time.time() - start_time > (60 * 3):
                self.root.warn_log("没有找到应用程序，退出程序")
                return False
            if self.project.config.app is False:
                print(".")
            else:
                # 监视应用程序运行情况
                self.thread_app()
                break
            time.sleep(5)
        # 置顶窗口
        self.project.config.move_app(True)
        self.root.info_log("准备就绪，脚本开始运行")
        self.root.project = self.project
        # 创建运行进程
        if not self.is_ddt:
            self.set_process()
        # 创建线程
        else:
            self.set_thread()

    def get_next_scene(self):
        """获取下一个场景列表"""
        self.id += 1
        if self.id > len(self.project.config.run_order):
            self.root.end()
        _res = {}
        for i in self.project.config.run_order:
            if i["id"] == self.id:
                _res = i
        if _res == {}:
            self.root.err_log("设置中场景执行顺序有误！必须从1开始连贯的整数")
            self.root.end()
        return _res

    def execute(self, operation_list, result):
        for i in range(len(operation_list)):
            operation = get_min_id_value(operation_list)
            if type(operation) == Click:
                self.click(operation, result)
                continue
            if type(operation) == Drag:
                self.drag(operation, result)
                continue
            if type(operation) == Input:
                self.input(operation, result)
                continue

    def click(self, operation, result):
        self.root.info_log(f"[{operation.id}]:点击操作")
        if operation.translate.replace(" ", "") == "0,0":
            self.root.info_log(f"点击{result[0]},{result[1]}")
            PyMouse().click(int(result[0]), int(result[1]))
        else:
            PyMouse().click(int(operation.translate.split(",")[0]), int(operation.translate.split(",")[1]))
            self.root.info_log(f"点击{operation.translate.split(',')[0]},"
                               f"{operation.translate.split(',')[1]}")
        self.root.info_log(f"延迟{operation.delay_time}秒")
        time.sleep(float(operation.delay_time))

    def drag(self, operation, result):
        self.root.info_log(f"[{operation.id}]:拖拽操作")
        if operation.translate.replace(" ", "") == "0,0" and \
                operation.translate_drag.replace(" ", "") == "0,0":
            self.root.info_log(f"拖拽位置都为识别坐标，退化为点击操作")
            self.click(operation, result)
            return 1
        if operation.translate.replace(" ", "") == "0,0":
            operation.translate = f"{result[0]},{result[1]}"
        if operation.translate_drag.replace(" ", "") == "0,0":
            operation.translate_drag = f"{result[0]},{result[1]}"
        displacement_x = int(operation.translate_drag.split(",")[0]) - int(operation.translate.split(",")[0])
        displacement_y = int(operation.translate_drag.split(",")[1]) - int(operation.translate.split(",")[1])
        pyautogui.moveTo(int(operation.operation.split(",")[0]),
                         int(operation.operation.split(",")[1]), 0.5)
        pyautogui.dragRel(displacement_x, displacement_y, 0.5)
        self.root.info_log(f"延迟{operation.delay_time}秒")
        time.sleep(float(operation.delay_time))

    def input(self, operation, result):
        self.root.info_log(f"[{operation.id}]:输入文本")
        if operation.type == "text" or "text/http":
            if operation.text == "enter" or\
                    operation.text == "ENTER" or\
                    operation.text == "Enter":
                self.root.info_log(f"输入<Enter>键")
                pynput.keyboard.Controller().press(pynput.keyboard.Key.enter)
                pynput.keyboard.Controller().release(pynput.keyboard.Key.enter)
                return 1
            self.root.info_log(f"输入{operation.text}")
            for i in operation.text:
                pynput.keyboard.Controller().press(i)
                pynput.keyboard.Controller().release(i)
                time.sleep(0.1)
            self.root.info_log(f"延迟{operation.delay_time}秒")
            time.sleep(float(operation.delay_time))
            return 1
        if operation.type == "http":
            try:
                _res = requests.get(operation.text)
            except Exception as e:
                self.root.warn_log(f"http请求异常：{operation.text}, {e}")
                return 0
            self.root.info_log(f"输入：{_res.text}")
            if _res.text == "enter" or\
                    _res.text == "ENTER" or\
                    _res.text == "Enter":
                self.root.info_log(f"输入<Enter>键")
                pynput.keyboard.Controller().press(pynput.keyboard.Key.enter)
                pynput.keyboard.Controller().release(pynput.keyboard.Key.enter)
                return 1
            for i in _res.text:
                pynput.keyboard.Controller().press(i)
                pynput.keyboard.Controller().release(i)
                time.sleep(0.1)
            self.root.info_log(f"延迟{operation.delay_time}秒")
            time.sleep(float(operation.delay_time))
        self.root.warn_log(f"输入类型错误:{operation.type},跳过...")
        return 0


def gray(img):
    # 灰度化处理
    gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    return gray_img


def find_position(screen, template, threshold):
    image_x, image_y = template.shape[:2]
    result = cv2.matchTemplate(screen, template, cv2.TM_CCOEFF_NORMED)  # 找图函数
    min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
    if max_val > threshold:
        center = (max_loc[0] + image_y / 2, max_loc[1] + image_x / 2)
        return center  # 坐标
    else:
        return False


def get_min_id_value(_list):
    _res = Operation()
    _res.id = 9999
    index = 0
    _index = 0
    if len(_list) == 1:
        return _list[0]
    for i in _list:
        if int(i.id) < int(_res.id):
            _res = i
            index = _index
        _index += 1
    _list.pop(index)
    return _res


if __name__ == '__main__':
    app_name = None
    det = Det(project="D:\zhuchun\doc\少年三国志2")
    det.run()
